#!/bin/bash

# Pi-Apps style installer for Minecraft Java Server with custom server directory support.
# Default server folder: $HOME/Minecraft-Java-Server
# Service name: minecraft-server
# Screen session: Minecraft_Server
# Desktop launcher: Minecraft Java Server

status "This script helps you setup a Minecraft-Java-Server (for any version)."

SERVICE_NAME="minecraft-server"
SCREEN_NAME="Minecraft_Server"
SERVER_FOLDER_NAME="Minecraft-Java-Server"
DESKTOP_FILE="$HOME/.local/share/applications/Minecraft-Java-Server.desktop"
CONFIG_DIR="$HOME/.config/minecraft-java-server"
CONFIG_FILE="$CONFIG_DIR/server_dir"
DEFAULT_SERVER_DIR="$HOME/$SERVER_FOLDER_NAME"


ensure_server_dir() {
  # Create the server folder. External drives under /media sometimes need sudo for
  # folder creation, but the Minecraft server still needs the regular user to be
  # able to write inside the final folder.

  # check if folder exists and is writable
  if [ -w ${server_dir%/*} ]; then
    mkdir -p "$server_dir" 2>/dev/null || error "Could not create server directory: $server_dir"
  else
    sudo mkdir -p "$server_dir" 2>/dev/null || error "Could not create server directory: $server_dir"
    # Best effort ownership/permissions. On exFAT these may be ignored, because
    # ownership comes from mount options, but this helps on ext4 and normal folders.
    sudo chown "$USER":"$USER" "$server_dir" 2>/dev/null || true
    sudo chmod 775 "$server_dir" 2>/dev/null || true
    touch "$server_dir/.write-test" 2>/dev/null || error "The chosen server directory exists, but is not writable by $USER:
$server_dir

Try this outside Pi-Apps:
touch \"$server_dir/test-write.txt\"

If that fails too, the path/drive is mounted without write access for $USER."
    rm -f "$server_dir/.write-test"
  fi
}

# allow user to skip installation if executed from an update
if [[ "$1" == "update" ]]; then
  description="The Minecraft Java Server script was executed as part of an update to improve platform support.\nWould you like to check for newer versions of Minecraft to upgrade your server to or exit now?"
  userinput_func "$description" "Yes, check for newer Minecraft versions and upgrade my install" "No, leave my install alone and exit now"
  if [ "$output" == "No, leave my install alone and exit now" ]; then
    status "User chose to exit the Minecraft Java Server script as part of an update without reinstallation."
    exit 0
  fi
fi

# add jq to use in script
install_packages jq || exit 1

choose_server_directory() {
  # On update, reuse the previously chosen directory when possible.
  if [[ "$1" == "update" ]] && [ -f "$CONFIG_FILE" ]; then
    server_dir="$(head -n 1 "$CONFIG_FILE")"
    if [ -n "$server_dir" ]; then
      status "Using existing Minecraft server folder:\n$server_dir"
      ensure_server_dir
      return
    fi
  fi

  description="Where do you want your Minecraft Java Server files to live?\n\nThis installer defaults to:\n$DEFAULT_SERVER_DIR\n\nYou can also choose an already-mounted drive path, such as /media/$USER/MySSD/$SERVER_FOLDER_NAME.\n\nThe selected folder will be used for server files, start-server.sh, the desktop launcher, and the systemd service."

  table=("Home folder: $DEFAULT_SERVER_DIR")

  # Add currently mounted user media drives, if any. This does not permanently mount them.
  if [ -d "/media/$USER" ]; then
    while IFS= read -r mount_dir; do
      [ -d "$mount_dir" ] || continue
      candidate="$mount_dir/$SERVER_FOLDER_NAME"
      table+=("Mounted drive: $candidate")
    done < <(find "/media/$USER" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | sort)
  fi

  table+=("Custom path")

  userinput_func "$description" "${table[@]}"
  selected_choice="$output"

  case "$selected_choice" in
    "Home folder: "*)
      server_dir="$DEFAULT_SERVER_DIR"
      ;;
    "Mounted drive: "*)
      server_dir="${selected_choice#Mounted drive: }"
      ;;
    "Custom path")
      server_dir=$(yad --center --wrap \
        --text "Enter the full path where the Minecraft server should be installed.\n\nExamples:\n$HOME/$SERVER_FOLDER_NAME\n/media/$USER/MySSD/$SERVER_FOLDER_NAME\n\nAvoid spaces in the path." \
        --entry \
        --entry-text "$DEFAULT_SERVER_DIR" \
        --button="Use this path":0)
      ;;
    *)
      server_dir="$DEFAULT_SERVER_DIR"
      ;;
  esac

  if [ -z "$server_dir" ]; then
    error "No server directory was selected. Exiting."
  fi

  # Expand leading ~ if user typed it.
  server_dir="${server_dir/#\~/$HOME}"

  # Normalize path if readlink supports -m.
  if command -v readlink >/dev/null 2>&1; then
    server_dir="$(readlink -m "$server_dir")"
  fi

  # Keep this simple and safe for systemd paths.
  if [[ "$server_dir" =~ [[:space:]] ]]; then
    error "The chosen path contains spaces:\n$server_dir\n\nPlease choose a path without spaces."
  fi


  ensure_server_dir

  mkdir -p "$CONFIG_DIR"
  printf '%s\n' "$server_dir" > "$CONFIG_FILE"

  status "Minecraft server files will be installed in:\n$server_dir"
}

choose_server_directory "$1"

ensure_server_dir
cd "$server_dir" || error "Could not enter Minecraft server folder."

description="What type of Minecraft server\ndo you want to run?"
table=("Vanilla" "Fabric" "Forge" "Paper" "Folia" "Purpur")
userinput_func "$description" "${table[@]}"
server_category="$output"

# Helper for PaperMC Fill v3 downloads.
# PaperMC documents Fill v3 at fill.papermc.io and recommends stable builds.
download_fill_project() {
  PROJECT="$1"
  VERSION="$2"
  builds_response=$(wget -O- "https://fill.papermc.io/v3/projects/${PROJECT}/versions/${VERSION}/builds") || error "Could not fetch $PROJECT builds for $VERSION."

  # Prefer the newest STABLE build. If none exists, fall back to the newest build so Folia/experimental projects can still be tested.
  build_json=$(echo "$builds_response" | jq -c '([.[] | select(.channel == "STABLE")] | last) // (last)')

  if [ -z "$build_json" ] || [ "$build_json" = "null" ]; then
    error "No $PROJECT build was found for version $VERSION."
  fi

  server_jar="$(echo "$build_json" | jq -r '.downloads."server:default".name')"
  download_url="$(echo "$build_json" | jq -r '.downloads."server:default".url')"
  channel="$(echo "$build_json" | jq -r '.channel // "unknown"')"

  if [ -z "$server_jar" ] || [ "$server_jar" = "null" ] || [ -z "$download_url" ] || [ "$download_url" = "null" ]; then
    error "Could not determine $PROJECT server jar or download URL for $VERSION."
  fi

  status "Selected $PROJECT $VERSION build channel: $channel"
  wget -O "$server_jar" "$download_url" || exit 1
}

case "$server_category" in
  "Fabric")
    available_versions=$(wget -O- https://meta.fabricmc.net/v2/versions/game | jq -r '.[] | select( .stable == true ) | .version')
    description="What version of $server_category do you want to run?"
    table=($available_versions)
    userinput_func "$description" "${table[@]}"
    server_version="$output"
    status "Selected $server_category version $server_version"
    loader_version=$(wget -O- https://meta.fabricmc.net/v2/versions/loader/$server_version | jq -r 'first( .[] | .loader | .version )')
    installer_verison=$(wget -O- https://meta.fabricmc.net/v2/versions/installer | jq -r 'first( .[] | .version )')
    server_jar="fabric-server-mc.$server_version-loader.$loader_version-launcher.$installer_verison.jar"
    wget -O "$server_jar" "https://meta.fabricmc.net/v2/versions/loader/$server_version/$loader_version/$installer_verison/server/jar" || exit 1
    ;;
  "Forge")
    # remove versions without an "installer" form
    available_versions=$(wget -O- https://files.minecraftforge.net/net/minecraftforge/forge/maven-metadata.json | jq -r 'del((."1.1", ."1.2.3", ."1.2.4", ."1.2.5", ."1.3.2", ."1.4.0", ."1.4.1", ."1.4.2", ."1.4.3", ."1.4.4", ."1.4.5", ."1.4.6", ."1.4.7", ."1.5", ."1.5.1", ."1.5.2", ."1.6.1", ."1.6.2", ."1.6.3")) | keys_unsorted | reverse | .[]')
    description="What version of $server_category do you want to run?"
    table=($available_versions)
    userinput_func "$description" "${table[@]}"
    server_version="$output"
    status "Selected $server_category version $server_version"
    installer_version=$(wget -O- https://files.minecraftforge.net/net/minecraftforge/forge/maven-metadata.json | jq -r '.'\"$server_version\"' | [last] | .[]')

    # example file formats
    # https://maven.minecraftforge.net/net/minecraftforge/forge/1.18.1-39.0.63/forge-1.18.1-39.0.63-installer.jar
    # https://maven.minecraftforge.net/net/minecraftforge/forge/1.3.2-4.3.5.318/forge-1.3.2-4.3.5.318-universal.zip
    # https://maven.minecraftforge.net/net/minecraftforge/forge/1.2.5-3.4.9.171/forge-1.2.5-3.4.9.171-server.zip

    server_jar="forge-$installer_version-installer.jar"
    wget -O "$server_jar" "https://maven.minecraftforge.net/net/minecraftforge/forge/$installer_version/forge-$installer_version-installer.jar" || exit 1
    ;;
  "Paper")
    available_versions=$(wget -O- https://fill.papermc.io/v3/projects/paper/versions | jq -r '.versions.[].version.id')
    description="What version of $server_category do you want to run?"
    table=($available_versions)
    userinput_func "$description" "${table[@]}"
    server_version="$output"
    status "Selected $server_category version $server_version"
    download_fill_project "paper" "$server_version"
    ;;
  "Folia")
    available_versions=$(wget -O- https://fill.papermc.io/v3/projects/folia/versions | jq -r '.versions.[].version.id')
    description="What version of $server_category do you want to run?"
    table=($available_versions)
    userinput_func "$description" "${table[@]}"
    server_version="$output"
    status "Selected $server_category version $server_version"
    download_fill_project "folia" "$server_version"
    ;;
  "Purpur")
    available_versions=$(wget -O- https://api.purpurmc.org/v2/purpur/ | jq -r '.versions | .[]')
    description="What version of $server_category do you want to run?"
    table=($available_versions)
    userinput_func "$description" "${table[@]}"
    server_version="$output"
    status "Selected $server_category version $server_version"
    build_version=$(wget -O- https://api.purpurmc.org/v2/purpur/$server_version/ | jq -r '.builds.latest')
    server_jar="purpur-$server_version-$build_version.jar"
    wget -O "$server_jar" "https://api.purpurmc.org/v2/purpur/$server_version/$build_version/download" || exit 1
    ;;
  "Vanilla")
    available_versions=$(wget -O- https://piston-meta.mojang.com/mc/game/version_manifest_v2.json | jq -r '.versions | .[] | select ( .type == "release" ) | .id')
    description="What version of $server_category do you want to run?"
    table=($available_versions)
    userinput_func "$description" "${table[@]}"
    server_version="$output"
    status "Selected $server_category version $server_version"
    version_json="$(wget -O- https://piston-meta.mojang.com/mc/game/version_manifest_v2.json | jq -r '.versions | .[] | select ( .id == "'$server_version'" ) | .url')"
    server_jar="minecraft_server.$server_version.jar"
    wget -O "$server_jar" "$(wget -O- "$version_json" | jq -r '.downloads.server.url')" || exit 1
    ;;
  *)
    error "You did not choose an option, exiting the script."
    ;;
esac

while [[ ! -a "$server_jar" ]]; do
  description="Please use the website which has opened to download your chosen version of the Minecraft Java Server.\
  \nAdd your ${server_jar} file to the folder opened $server_dir and press OK.\
  \n\nYou have not added a ${server_jar}."
  table=("OK")
  userinput_func "$description" "${table[@]}"
done

# use * to get full name of server jar
server_jar=$(echo ${server_jar})

if [ -z "$server_version" ]; then
  # tell user to choose java 8, 17, 21, or 25
  description="In order to run Minecraft, the game requires a specific version of Java.\
  \nPlease choose below which version of java the server of your choice should run with.\
  \nJava versions can be 8, 17, 21, or 25.\
  \n\nAll versions of Minecraft before and including Minecraft 1.16.5 should use Java 8.\
  \nMinecraft 1.17-1.20 should use java 17.\
  \nMinecraft 1.21-1.21.11 should use java 21.\
  \nMinecraft 26.1+ should use java 25."
  table=("Java 25" "Java 21" "Java 17" "Java 8")
  userinput_func "$description" "${table[@]}"
  java_selection="$output"
else
  # we know the version of minecraft in use
  # choose the appropriate version of java based on that
  # version <= 1.16.5 java 8
  # 1.17 <= version < 1.21 java 17
  # 1.21 <= version < 26.1 java 21
  # version >= 26.1 java 25
  function version { echo "$@" | awk -F. '{ printf("%d%03d%03d\n", $1,$2,$3); }'; }
  if [ $(version $server_version) -le $(version "1.16.5") ]; then
    echo "Using Java 8 by default"
    java_selection="Java 8"
  elif [ $(version $server_version) -ge $(version "26.1") ]; then
    echo "Using Java 25 by default"
    java_selection="Java 25"
  elif [ $(version $server_version) -ge $(version "1.21") ]; then
    echo "Using Java 21 by default"
    java_selection="Java 21"
  elif [ $(version $server_version) -ge $(version "1.17") ]; then
    echo "Using Java 17 by default"
    java_selection="Java 17"
  else
    description="WARNING: We don't know how you got here... your version $server_version does not seem to be a normal minecraft format.\
    \n\nIn order to run Minecraft, the game requires a specific version of Java.\
    \nPlease choose below which version of java the server of your choice should run with.\
    \nJava versions can be 8, 17, 21, or 25.\
    \n\nAll version of Minecraft before and including Minecraft 1.16.5 should use Java 8.\
    \nMinecraft 1.17-1.20 should use java 17.\
    \nMinecraft 1.21-1.21.11 should use java 21.\
    \nMinecraft 26.1+ should use java 25."
    table=("Java 25" "Java 21" "Java 17" "Java 8")
    userinput_func "$description" "${table[@]}"
    java_selection="$output"
  fi
fi

install_packages lsb-release wget gpg screen || exit 1

case "$java_selection" in
  "Java 21"|"Java 25")
    case "$arch" in
      "32")
        error "User error: Java 21+ is not available for your platform from Adoptium. All 32-bit based architectures are being deprecated and removed by Java. Please use a 64-bit OS. Exiting the script."
        ;;
      *)
        adoptium_installer || exit 1
        ;;
    esac
    ;;
  "Java 8"|"Java 17")
    adoptium_installer || exit 1
    ;;
  *)
    error "No java version selected, exiting the script"
    ;;
esac

case "$arch" in
  "64")
    dpkg_arch="arm64"
    ;;
  "32")
    dpkg_arch="armhf"
    ;;
  *)
    error "arch variable is not set, can not continue"
    ;;
esac

case "$java_selection" in
  "Java 8")
    install_packages temurin-8-jre || exit 1
    java_location="/usr/lib/jvm/temurin-8-jre-$dpkg_arch/bin/java"
    ;;
  "Java 17")
    install_packages temurin-17-jre || exit 1
    java_location="/usr/lib/jvm/temurin-17-jre-$dpkg_arch/bin/java"
    ;;
  "Java 21")
    if package_available temurin-21-jre ;then
      install_packages temurin-21-jre || exit 1
      java_location="/usr/lib/jvm/temurin-21-jre-$dpkg_arch/bin/java"
    else
      error "Java 21 is not available for your platform from Adoptium. Exiting the script."
    fi
    ;;
  "Java 25")
    if package_available temurin-25-jre ;then
      install_packages temurin-25-jre || exit 1
    else
      error "Java 25 is not available for your platform from Adoptium. Exiting the script."
    fi
    java_location="/usr/lib/jvm/temurin-25-jre-$dpkg_arch/bin/java"
    ;;
esac

jvm_test="1"
jvm_args=""
while [[ $jvm_test != 0 ]]; do
  # userinput jvm_args
  jvm_args=$(yad --center --wrap --text "Would you like to input any JVM Arguments?\nA default list is prepopulated in the text box below.\n\nThe most important arguments, -Xmx and -Xms, are at the beginning and define the max RAM and initial RAM, respectively.\nYou should specify a value a quarter less than your total RAM in bytes, kilobytes(k), megabytes(m), or gigabytes(g).\nHint, use your keyboard Home key to see the start of this list." --entry --entry-text="-Xmx1200m -Xms1200m -XX:+UseG1GC -XX:+ParallelRefProcEnabled -XX:MaxGCPauseMillis=200 -XX:+UnlockExperimentalVMOptions -XX:+DisableExplicitGC -XX:+AlwaysPreTouch -XX:G1NewSizePercent=30 -XX:G1MaxNewSizePercent=40 -XX:G1HeapRegionSize=8M -XX:G1ReservePercent=20 -XX:G1HeapWastePercent=5 -XX:G1MixedGCCountTarget=4 -XX:InitiatingHeapOccupancyPercent=15 -XX:G1MixedGCLiveThresholdPercent=90 -XX:G1RSetUpdatingPauseTimePercent=5 -XX:SurvivorRatio=32 -XX:+PerfDisableSharedMem -XX:MaxTenuringThreshold=1" --button="Validate JVM Arguments":0)
  $java_location $jvm_args -version
  jvm_test="$?"
  if [[ $jvm_test != 0 ]]; then
    echo "Your JVM Arguments were invalid. Your input was: $jvm_args"
    yad --center --text "OH NO! The JVM Arguments you inputed are either invalid (like a typo) or incompatible with the selected java version.\n\nRefer to the terminal log immeditately above to determine the issue and try again." --button="Try New JVM Arguments":0
  fi
done

if [[ "$server_category" == "Forge" ]]; then
  # run the server installer
  rm -f start-server.sh
  $java_location -jar "$server_dir/${server_jar}" --installServer || error "Failed to install forge server"
  rm -f ${server_jar}

  # forge has MANY ways to launch depending on the version
  if [[ -f "minecraftforge-universal-$installer_version.jar" ]]; then
    server_jar="minecraftforge-universal-$installer_version.jar"
  elif [[ -f "forge-$installer_version-universal.jar" ]]; then
    server_jar="forge-$installer_version-universal.jar"
  elif [[ -f "forge-$installer_version-shim.jar" ]]; then
    server_jar="forge-$installer_version-shim.jar"
  elif [[ -f "forge-$installer_version.jar" ]]; then
    server_jar="forge-$installer_version.jar"
  elif [[ -f "run.sh" ]]; then
    # set server_jar to empty to skip start-server.sh universal script creation
    server_jar=""
    sed -i "s:java:${java_location}:g" run.sh
    sed -i 's:"$@":nogui:g' run.sh
    echo "$jvm_args" > user_jvm_args.txt
    mv run.sh start-server.sh
    chmod +x start-server.sh
  else
    error "We have never seen this new obscure Forge format from $installer_version. Please report this error to pi-apps"
  fi
fi

if [ -n "$server_jar" ]; then
  cat > "$server_dir/start-server.sh" <<EOF
#!/bin/bash
echo 'Minecraft Server starting'
cd "$server_dir" || exit 1
"$java_location" $jvm_args -jar "$server_dir/${server_jar}" nogui || (echo -e '\e[91mMinecraft Server has crashed or could not start\e[0m'; sleep 10)
echo 'Minecraft Server has stopped'
sleep 3
EOF
  chmod +x "$server_dir/start-server.sh"
fi

# sign eula without starting server
echo "eula=true" > "$server_dir/eula.txt"

# make mods directory
mkdir -p "$server_dir/mods"

mkdir -p "$HOME/.local/share/applications"
tee "$DESKTOP_FILE" <<EOF
[Desktop Entry]
Name=Minecraft Java Server
Exec=bash -c "sudo systemctl start $SERVICE_NAME; sleep 3; screen -r $SCREEN_NAME; if [ \$? == 1 ]; then echo 'The minecraft server has exited without the service stopping, restarting the service for you'; sudo systemctl restart $SERVICE_NAME; sleep 5; screen -r $SCREEN_NAME; fi"
Path=$server_dir
Icon=${DIRECTORY}/apps/Minecraft Java Server/icon-64.png
Type=Application
Categories=Game
Terminal=true
EOF

# used for absolute executable path (necessary on some OSes)
screen_location=$(which screen)
sleep_location=$(which sleep)

sudo tee "/etc/systemd/system/$SERVICE_NAME.service" <<EOF
[Unit]
Description=Minecraft Java Server
RequiresMountsFor=$server_dir
After=network.target

[Service]
RemainAfterExit=yes
WorkingDirectory=$server_dir
User=$USER
# Start Screen, Java, and Minecraft
ExecStart=$screen_location -S $SCREEN_NAME -U -d -m $server_dir/start-server.sh
# Tell Minecraft to gracefully stop.
# Ending Minecraft will terminate Java
# systemd will kill Screen after the 10-second delay. No explicit kill for Screen needed
ExecStop=$screen_location -p 0 -S $SCREEN_NAME -X eval 'stuff "say SERVER SHUTTING DOWN. Saving map..."\015'
ExecStop=$screen_location -p 0 -S $SCREEN_NAME -X eval 'stuff "save-all"\015'
ExecStop=$screen_location -p 0 -S $SCREEN_NAME -X eval 'stuff "stop"\015'
ExecStop=$sleep_location 10

[Install]
WantedBy=multi-user.target
EOF

echo

# update services
sudo systemctl daemon-reload
sudo systemctl disable "$SERVICE_NAME"

status "Your Minecraft server folder is:\n$server_dir"
status "Your IP address is one of the following: 
$(hostname -I | grep -Eo '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}')"
status "The IP address is used to play on your local network
(or you can create a port forward on your internet router/modem at port 25565 to play over the internet)."
echo
status_green "You might want to copy this down:
To run: Menu -> Games -> Minecraft Java Server
Attach to a server in the background with: screen -r $SCREEN_NAME
Detach from a running server session with: CTRL+A then D
To start from a terminal: sudo systemctl start $SERVICE_NAME
To stop the server: sudo systemctl stop $SERVICE_NAME
To start now, start on boot, and stop on shutdown: sudo systemctl enable --now $SERVICE_NAME
"
